package io.cici.cc.mybatis.plus.core.override;

import io.cici.cc.mybatis.lite.reflection.ExceptionUtil;
import io.cici.cc.mybatis.lite.session.Session;
import io.cici.cc.mybatis.plus.core.toolkit.CollectionUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;


public class MybatisMapperProxy<T> implements InvocationHandler {

    private final Session session;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> map;

    public MybatisMapperProxy(Session session, Class<T> mapperInterface,
                              Map<Method, MapperMethodInvoker> methodCache) {
        this.session = session;
        this.mapperInterface = mapperInterface;
        this.map = methodCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                return mapperMethodInvoker(method).invoke(proxy, method, args, session);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    private MapperMethodInvoker mapperMethodInvoker(Method method) throws Throwable {
        try {
            return CollectionUtils.computeIfAbsent(map, method, m -> {
                if (m.isDefault()) {
                    throw new IllegalArgumentException();
                } else {
                    return new MapperMethodInvokerImpl(new MybatisMapperMethod(mapperInterface, method, session.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }


    interface MapperMethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args, Session session) throws Throwable;
    }

    private static class MapperMethodInvokerImpl implements MapperMethodInvoker {
        private final MybatisMapperMethod mybatisMapperMethod;

        public MapperMethodInvokerImpl(MybatisMapperMethod mybatisMapperMethod) {
            super();
            this.mybatisMapperMethod = mybatisMapperMethod;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args, Session session) throws Throwable {
            return mybatisMapperMethod.execute(session, args);
        }
    }

}
